"""
Session Controller

This module manages user sessions, authentication, multi-user support,
educational scenarios, and learning progress tracking for the AI Historical
Simulation Platform.
"""

import asyncio
import json
import logging
import uuid
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Any, Set
from dataclasses import dataclass, asdict
from enum import Enum

logger = logging.getLogger(__name__)


class SessionStatus(Enum):
    """Session status enumeration."""
    ACTIVE = "active"
    PAUSED = "paused"
    ENDED = "ended"
    EXPIRED = "expired"


class UserRole(Enum):
    """User role enumeration."""
    STUDENT = "student"
    TEACHER = "teacher"
    RESEARCHER = "researcher"
    ADMIN = "admin"


@dataclass
class User:
    """Represents a platform user."""
    user_id: str
    username: str
    email: Optional[str]
    role: UserRole
    created_at: datetime
    last_login: Optional[datetime]
    preferences: Dict[str, Any]
    learning_progress: Dict[str, Any]
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert user to dictionary."""
        return {
            'user_id': self.user_id,
            'username': self.username,
            'email': self.email,
            'role': self.role.value,
            'created_at': self.created_at.isoformat(),
            'last_login': self.last_login.isoformat() if self.last_login else None,
            'preferences': self.preferences,
            'learning_progress': self.learning_progress
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'User':
        """Create user from dictionary."""
        return cls(
            user_id=data['user_id'],
            username=data['username'],
            email=data.get('email'),
            role=UserRole(data['role']),
            created_at=datetime.fromisoformat(data['created_at']),
            last_login=datetime.fromisoformat(data['last_login']) if data.get('last_login') else None,
            preferences=data.get('preferences', {}),
            learning_progress=data.get('learning_progress', {})
        )


@dataclass
class Session:
    """Represents a user session."""
    session_id: str
    user_id: str
    started_at: datetime
    last_activity: datetime
    status: SessionStatus
    figure_interactions: List[str]
    conversation_count: int
    session_type: str  # 'interactive', 'educational', 'research'
    metadata: Dict[str, Any]
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert session to dictionary."""
        return {
            'session_id': self.session_id,
            'user_id': self.user_id,
            'started_at': self.started_at.isoformat(),
            'last_activity': self.last_activity.isoformat(),
            'status': self.status.value,
            'figure_interactions': self.figure_interactions,
            'conversation_count': self.conversation_count,
            'session_type': self.session_type,
            'metadata': self.metadata
        }
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Session':
        """Create session from dictionary."""
        return cls(
            session_id=data['session_id'],
            user_id=data['user_id'],
            started_at=datetime.fromisoformat(data['started_at']),
            last_activity=datetime.fromisoformat(data['last_activity']),
            status=SessionStatus(data['status']),
            figure_interactions=data.get('figure_interactions', []),
            conversation_count=data.get('conversation_count', 0),
            session_type=data.get('session_type', 'interactive'),
            metadata=data.get('metadata', {})
        )


@dataclass
class EducationalScenario:
    """Represents an educational scenario."""
    scenario_id: str
    name: str
    description: str
    learning_objectives: List[str]
    required_figures: List[str]
    difficulty_level: str  # 'beginner', 'intermediate', 'advanced'
    estimated_duration: int  # minutes
    created_by: str
    tags: List[str]
    content: Dict[str, Any]
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert scenario to dictionary."""
        return asdict(self)
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'EducationalScenario':
        """Create scenario from dictionary."""
        return cls(**data)


class SessionController:
    """
    Controls user sessions, authentication, and educational scenarios.
    
    This class provides:
    - User session management and authentication
    - Multi-user support with concurrent figure interactions
    - Educational scenario creation and management
    - Learning progress tracking and analytics
    """
    
    def __init__(self, platform):
        """
        Initialize the Session Controller.
        
        Args:
            platform: Reference to the AIHistoricalSimulationPlatform
        """
        self.platform = platform
        self.data_dir = Path('data/sessions')
        self.users_file = self.data_dir / 'users.json'
        self.scenarios_file = self.data_dir / 'scenarios.json'
        
        # In-memory storage
        self.active_sessions: Dict[str, Session] = {}
        self.users: Dict[str, User] = {}
        self.educational_scenarios: Dict[str, EducationalScenario] = {}
        self.concurrent_limits: Dict[str, int] = {
            'student': 1,
            'teacher': 5,
            'researcher': 10,
            'admin': 50
        }
        
        # Session management
        self.session_timeout = timedelta(hours=2)
        self.current_session: Optional[Session] = None
        
        # Create data directory
        self.data_dir.mkdir(parents=True, exist_ok=True)
        
        logger.info("SessionController initialized")
    
    async def initialize(self) -> None:
        """Initialize the session controller."""
        logger.info("Initializing Session Controller")
        
        try:
            # Load existing users
            await self._load_users()
            
            # Load educational scenarios
            await self._load_scenarios()
            
            # Create default scenarios if none exist
            if not self.educational_scenarios:
                await self._create_default_scenarios()
            
            # Start session cleanup task
            asyncio.create_task(self._session_cleanup_task())
            
            logger.info("Session Controller initialized successfully")
            
        except Exception as e:
            logger.error(f"Failed to initialize session controller: {e}")
            raise
    
    async def create_user(self, username: str, email: Optional[str] = None, 
                         role: UserRole = UserRole.STUDENT) -> User:
        """
        Create a new user.
        
        Args:
            username: Username
            email: Optional email address
            role: User role
            
        Returns:
            Created user
        """
        # Check if username already exists
        if any(user.username == username for user in self.users.values()):
            raise ValueError(f"Username '{username}' already exists")
        
        user_id = str(uuid.uuid4())
        user = User(
            user_id=user_id,
            username=username,
            email=email,
            role=role,
            created_at=datetime.now(),
            last_login=None,
            preferences={
                'preferred_figures': [],
                'conversation_style': 'balanced',
                'difficulty_level': 'intermediate'
            },
            learning_progress={
                'conversations_completed': 0,
                'figures_interacted': [],
                'scenarios_completed': [],
                'achievement_points': 0
            }
        )
        
        self.users[user_id] = user
        await self._save_users()
        
        logger.info(f"Created new user: {username} ({role.value})")
        return user
    
    async def authenticate_user(self, username: str) -> Optional[User]:
        """
        Authenticate a user (simplified authentication).
        
        Args:
            username: Username to authenticate
            
        Returns:
            Authenticated user or None
        """
        for user in self.users.values():
            if user.username == username:
                user.last_login = datetime.now()
                await self._save_users()
                logger.info(f"User authenticated: {username}")
                return user
        
        logger.warning(f"Authentication failed for username: {username}")
        return None
    
    async def start_session(self, user_id: str, session_type: str = 'interactive') -> Session:
        """
        Start a new user session.
        
        Args:
            user_id: User ID
            session_type: Type of session ('interactive', 'educational', 'research')
            
        Returns:
            Created session
        """
        user = self.users.get(user_id)
        if not user:
            raise ValueError(f"User {user_id} not found")
        
        # Check concurrent session limits
        user_sessions = [s for s in self.active_sessions.values() 
                        if s.user_id == user_id and s.status == SessionStatus.ACTIVE]
        
        if len(user_sessions) >= self.concurrent_limits[user.role.value]:
            raise ValueError(f"User has reached maximum concurrent sessions limit")
        
        session_id = str(uuid.uuid4())
        session = Session(
            session_id=session_id,
            user_id=user_id,
            started_at=datetime.now(),
            last_activity=datetime.now(),
            status=SessionStatus.ACTIVE,
            figure_interactions=[],
            conversation_count=0,
            session_type=session_type,
            metadata={
                'user_role': user.role.value,
                'session_features': []
            }
        )
        
        self.active_sessions[session_id] = session
        self.current_session = session
        
        logger.info(f"Started new session: {session_id} for user: {user.username}")
        return session
    
    async def end_session(self, session_id: str) -> bool:
        """
        End a user session.
        
        Args:
            session_id: Session ID to end
            
        Returns:
            True if successfully ended, False otherwise
        """
        session = self.active_sessions.get(session_id)
        if not session:
            return False
        
        session.status = SessionStatus.ENDED
        session.last_activity = datetime.now()
        
        # Update user learning progress
        await self._update_learning_progress(session)
        
        # Archive session data
        await self._archive_session(session)
        
        # Remove from active sessions
        del self.active_sessions[session_id]
        
        if self.current_session and self.current_session.session_id == session_id:
            self.current_session = None
        
        logger.info(f"Ended session: {session_id}")
        return True
    
    def get_current_session(self) -> Optional[Session]:
        """Get the current active session."""
        return self.current_session
    
    def get_user_sessions(self, user_id: str) -> List[Session]:
        """
        Get all active sessions for a user.
        
        Args:
            user_id: User ID
            
        Returns:
            List of active sessions
        """
        return [s for s in self.active_sessions.values() if s.user_id == user_id]
    
    async def update_session_activity(self, session_id: str, activity_data: Dict[str, Any]) -> bool:
        """
        Update session activity.
        
        Args:
            session_id: Session ID
            activity_data: Activity data to record
            
        Returns:
            True if successfully updated, False otherwise
        """
        session = self.active_sessions.get(session_id)
        if not session:
            return False
        
        session.last_activity = datetime.now()
        
        # Record figure interactions
        if 'figure_name' in activity_data:
            figure_name = activity_data['figure_name']
            if figure_name not in session.figure_interactions:
                session.figure_interactions.append(figure_name)
        
        # Update conversation count
        if activity_data.get('type') == 'conversation':
            session.conversation_count += 1
        
        # Store activity in metadata
        if 'activities' not in session.metadata:
            session.metadata['activities'] = []
        
        session.metadata['activities'].append({
            'timestamp': datetime.now().isoformat(),
            'data': activity_data
        })
        
        return True
    
    async def create_educational_scenario(self, name: str, description: str,
                                        learning_objectives: List[str],
                                        required_figures: List[str],
                                        difficulty_level: str,
                                        estimated_duration: int,
                                        created_by: str,
                                        content: Dict[str, Any]) -> EducationalScenario:
        """
        Create a new educational scenario.
        
        Args:
            name: Scenario name
            description: Scenario description
            learning_objectives: List of learning objectives
            required_figures: List of required historical figures
            difficulty_level: Difficulty level
            estimated_duration: Estimated duration in minutes
            created_by: Creator user ID
            content: Scenario content
            
        Returns:
            Created educational scenario
        """
        scenario_id = str(uuid.uuid4())
        scenario = EducationalScenario(
            scenario_id=scenario_id,
            name=name,
            description=description,
            learning_objectives=learning_objectives,
            required_figures=required_figures,
            difficulty_level=difficulty_level,
            estimated_duration=estimated_duration,
            created_by=created_by,
            tags=[],
            content=content
        )
        
        self.educational_scenarios[scenario_id] = scenario
        await self._save_scenarios()
        
        logger.info(f"Created educational scenario: {name}")
        return scenario
    
    def get_educational_scenarios(self, difficulty_level: Optional[str] = None) -> List[EducationalScenario]:
        """
        Get educational scenarios, optionally filtered by difficulty.
        
        Args:
            difficulty_level: Optional difficulty filter
            
        Returns:
            List of educational scenarios
        """
        scenarios = list(self.educational_scenarios.values())
        
        if difficulty_level:
            scenarios = [s for s in scenarios if s.difficulty_level == difficulty_level]
        
        return sorted(scenarios, key=lambda s: s.name)
    
    async def start_educational_scenario(self, user_id: str, scenario_id: str) -> Session:
        """
        Start an educational scenario session.
        
        Args:
            user_id: User ID
            scenario_id: Scenario ID
            
        Returns:
            Created educational session
        """
        scenario = self.educational_scenarios.get(scenario_id)
        if not scenario:
            raise ValueError(f"Educational scenario {scenario_id} not found")
        
        # Start educational session
        session = await self.start_session(user_id, 'educational')
        
        # Add scenario information
        session.metadata.update({
            'scenario_id': scenario_id,
            'scenario_name': scenario.name,
            'required_figures': scenario.required_figures,
            'learning_objectives': scenario.learning_objectives,
            'progress': {
                'objectives_completed': [],
                'current_step': 0,
                'total_steps': len(scenario.content.get('steps', []))
            }
        })
        
        logger.info(f"Started educational scenario session: {scenario.name}")
        return session
    
    def get_learning_analytics(self, user_id: Optional[str] = None) -> Dict[str, Any]:
        """
        Get learning analytics and progress tracking.
        
        Args:
            user_id: Optional specific user ID, if None returns aggregate data
            
        Returns:
            Learning analytics data
        """
        if user_id:
            # Individual user analytics
            user = self.users.get(user_id)
            if not user:
                return {}
            
            user_sessions = [s for s in self.active_sessions.values() if s.user_id == user_id]
            
            return {
                'user_id': user_id,
                'username': user.username,
                'role': user.role.value,
                'total_sessions': len(user_sessions),
                'active_sessions': len([s for s in user_sessions if s.status == SessionStatus.ACTIVE]),
                'total_conversations': sum(s.conversation_count for s in user_sessions),
                'unique_figures_interacted': len(set().union(*[s.figure_interactions for s in user_sessions])),
                'learning_progress': user.learning_progress,
                'last_activity': max([s.last_activity for s in user_sessions]) if user_sessions else None
            }
        else:
            # Aggregate analytics
            all_sessions = list(self.active_sessions.values())
            
            return {
                'total_users': len(self.users),
                'total_sessions': len(all_sessions),
                'active_sessions': len([s for s in all_sessions if s.status == SessionStatus.ACTIVE]),
                'total_conversations': sum(s.conversation_count for s in all_sessions),
                'unique_figures_used': len(set().union(*[s.figure_interactions for s in all_sessions])),
                'user_roles_distribution': {
                    role.value: len([u for u in self.users.values() if u.role == role])
                    for role in UserRole
                },
                'session_types_distribution': {
                    session_type: len([s for s in all_sessions if s.session_type == session_type])
                    for session_type in ['interactive', 'educational', 'research']
                }
            }
    
    # Private methods
    
    async def _load_users(self) -> None:
        """Load users from file."""
        if self.users_file.exists():
            try:
                with open(self.users_file, 'r') as f:
                    users_data = json.load(f)
                
                for user_data in users_data:
                    user = User.from_dict(user_data)
                    self.users[user.user_id] = user
                
                logger.info(f"Loaded {len(self.users)} users")
                
            except Exception as e:
                logger.warning(f"Failed to load users: {e}")
    
    async def _save_users(self) -> None:
        """Save users to file."""
        try:
            users_data = [user.to_dict() for user in self.users.values()]
            with open(self.users_file, 'w') as f:
                json.dump(users_data, f, indent=2)
        except Exception as e:
            logger.error(f"Failed to save users: {e}")
    
    async def _load_scenarios(self) -> None:
        """Load educational scenarios from file."""
        if self.scenarios_file.exists():
            try:
                with open(self.scenarios_file, 'r') as f:
                    scenarios_data = json.load(f)
                
                for scenario_data in scenarios_data:
                    scenario = EducationalScenario.from_dict(scenario_data)
                    self.educational_scenarios[scenario.scenario_id] = scenario
                
                logger.info(f"Loaded {len(self.educational_scenarios)} educational scenarios")
                
            except Exception as e:
                logger.warning(f"Failed to load scenarios: {e}")
    
    async def _save_scenarios(self) -> None:
        """Save educational scenarios to file."""
        try:
            scenarios_data = [scenario.to_dict() for scenario in self.educational_scenarios.values()]
            with open(self.scenarios_file, 'w') as f:
                json.dump(scenarios_data, f, indent=2)
        except Exception as e:
            logger.error(f"Failed to save scenarios: {e}")
    
    async def _create_default_scenarios(self) -> None:
        """Create default educational scenarios."""
        default_scenarios = [
            {
                'name': 'Military Strategy Through Time',
                'description': 'Compare military strategies across different historical periods',
                'learning_objectives': [
                    'Understand evolution of military tactics',
                    'Compare ancient and modern warfare',
                    'Analyze leadership styles in military context'
                ],
                'required_figures': ['Napoleon Bonaparte', 'Julius Caesar'],
                'difficulty_level': 'intermediate',
                'estimated_duration': 30,
                'content': {
                    'steps': [
                        'Introduction to military history',
                        'Discussion with Napoleon about European campaigns',
                        'Conversation with Caesar about Roman tactics',
                        'Cross-temporal discussion on strategy evolution'
                    ]
                }
            },
            {
                'name': 'Renaissance Innovation Workshop',
                'description': 'Explore creativity and innovation during the Renaissance',
                'learning_objectives': [
                    'Understand Renaissance thinking methods',
                    'Explore interdisciplinary approaches',
                    'Learn about creative problem-solving'
                ],
                'required_figures': ['Leonardo da Vinci'],
                'difficulty_level': 'beginner',
                'estimated_duration': 20,
                'content': {
                    'steps': [
                        'Introduction to Renaissance period',
                        'Discussion with Leonardo about art and science',
                        'Explore creative methods and techniques'
                    ]
                }
            }
        ]
        
        for scenario_data in default_scenarios:
            await self.create_educational_scenario(
                name=scenario_data['name'],
                description=scenario_data['description'],
                learning_objectives=scenario_data['learning_objectives'],
                required_figures=scenario_data['required_figures'],
                difficulty_level=scenario_data['difficulty_level'],
                estimated_duration=scenario_data['estimated_duration'],
                created_by='system',
                content=scenario_data['content']
            )
    
    async def _update_learning_progress(self, session: Session) -> None:
        """Update user learning progress based on session data."""
        user = self.users.get(session.user_id)
        if not user:
            return
        
        # Update conversation count
        user.learning_progress['conversations_completed'] += session.conversation_count
        
        # Update figures interacted
        for figure in session.figure_interactions:
            if figure not in user.learning_progress['figures_interacted']:
                user.learning_progress['figures_interacted'].append(figure)
        
        # Award achievement points
        points_earned = session.conversation_count * 10
        if session.session_type == 'educational':
            points_earned *= 2  # Bonus for educational sessions
        
        user.learning_progress['achievement_points'] += points_earned
        
        # Mark scenario as completed if applicable
        if 'scenario_id' in session.metadata:
            scenario_id = session.metadata['scenario_id']
            if scenario_id not in user.learning_progress['scenarios_completed']:
                user.learning_progress['scenarios_completed'].append(scenario_id)
        
        await self._save_users()
    
    async def _archive_session(self, session: Session) -> None:
        """Archive completed session data."""
        try:
            archive_dir = self.data_dir / 'archived_sessions'
            archive_dir.mkdir(exist_ok=True)
            
            archive_file = archive_dir / f"session_{session.session_id}.json"
            with open(archive_file, 'w') as f:
                json.dump(session.to_dict(), f, indent=2)
                
        except Exception as e:
            logger.error(f"Failed to archive session {session.session_id}: {e}")
    
    async def _session_cleanup_task(self) -> None:
        """Background task to clean up expired sessions."""
        while True:
            try:
                current_time = datetime.now()
                expired_sessions = []
                
                for session_id, session in self.active_sessions.items():
                    if current_time - session.last_activity > self.session_timeout:
                        expired_sessions.append(session_id)
                
                for session_id in expired_sessions:
                    session = self.active_sessions[session_id]
                    session.status = SessionStatus.EXPIRED
                    await self.end_session(session_id)
                    logger.info(f"Expired session: {session_id}")
                
                # Sleep for 1 hour before next cleanup
                await asyncio.sleep(3600)
                
            except Exception as e:
                logger.error(f"Session cleanup task error: {e}")
                await asyncio.sleep(300)  # Retry in 5 minutes